﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using tuwien.auto.eibclient;
using System.Threading;
using KNXObjects.Addresses;
using KNXObjects;
using KNXObjects.Enum;

namespace KNXConnector.EIBD
{
    public class ObjectController : IObjectController
    {
        public List<ObjectBase> SetupObjectsList { get; set; }

        public void Start(List<ObjectBase> setupObjectsList)
        {
            SetupObjectsList = setupObjectsList;
            IsRunning = true;
            ListeningThread.Start(KNXConnection);
        }

        public void Stop()
        {
            if (IsRunning)
            {
                ListeningThread.Abort();
                KNXConnection.EIBClose();
            }
        }
        private ManualResetEventSlim ReadingResetEvent = new ManualResetEventSlim();

        private void OpenGroupSocket(object connection)
        {
            this.KNXConnection = connection as EIBConnection;
            if (connection != null)
            {
                if (KNXConnection.EIBOpen_GroupSocket(false) != -1)
                {
                    while (CheckInput() > 0) { }
                }
            }
        }

        public int CheckInput()
        {
            int len;
            EIBAddr dest = new EIBAddr();
            EIBAddr src = new EIBAddr();
            EIBBuffer buf = new EIBBuffer();
            //CustomLogger.WriteVerboseEntry("Check input");
            len = KNXConnection.EIBGetGroup_Src(buf, src, dest);
            if (len == -1)
            {
                //CustomLogger.WriteErrorEntry("Read failed", KNXLibrary.Enum.Priority.High);
                return len;

            }
            if (len < 2)
            {
                //CustomLogger.WriteErrorEntry("Invalid Packet", KNXLibrary.Enum.Priority.High);
            }
            PhysicalAddress physSrc = new PhysicalAddress(src.addr);
            GroupAddressType gaDest = new GroupAddressType(dest.addr);
            if ((buf.data[0] & 0x3) == 0x3 || (buf.data[1] & 0xc0) == 0xc0)
            {
                //CustomLogger.WriteErrorEntry(string.Format("Unknown APDU from {0} to {1}", physSrc, gaDest));
            }
            else
            {
                string message = "{0} from {1} to {2}: {3}";
                //KNXDeletage handler = null;
                bool resetEvent = false;
                switch (buf.data[1] & 0xc0)
                {
                    case 0x00:
                        message = string.Format(message, "Read", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        //handler = OnRead as KNXDeletage;
                        break;
                    case 0x40:
                        message = string.Format(message, "Response", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        //handler = OnResponse as KNXDeletage;
                        //ReadPending = false;
                        resetEvent = true;
                        ReadBuffer = buf;
                        ReadingResetEvent.Set();
                        break;
                    case 0x80:
                        message = string.Format(message, "Write", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        //handler = OnWrite as KNXDeletage;
                        break;
                }
                //if (handler != null)
                //{
                //    CustomLogger.WriteVerboseEntry(message);
                //    handler(physSrc, gaDest, buf);
                //    if (resetEvent)
                //        CheckInputResetEvent.Set();
                //}

            }
            return 1;

        }
        private static object s_ReadLockObject = new object();
        private EIBBuffer ReadBuffer { get; set; }

        private bool Read(GroupAddressType gad)
        {
            lock (s_ReadLockObject)
            {
                ReadBuffer = null;
                ReadingResetEvent.Reset();
                EIBBuffer buffer = new EIBBuffer(new byte[] { 0, 0});
                Write(gad, buffer);
                ReadingResetEvent.Wait(2000);
                if (ReadBuffer == null)
                    return false;
                else
                    return true;
            }
        }
        private void Write(GroupAddressType gad, EIBBuffer buffer)
        {
            if (gad.Value == 0)
                return;
            if (KNXConnection != null)
            {
                //CustomLogger.WriteVerboseEntry(string.Format("write gad: {0}, buf: {1}", ga, buffer));
                int len = KNXConnection.EIBSendGroup(gad.Value, buffer.data);
                if (len == -1)
                    throw new ApplicationException("Request failed");
            }
        }
        private void Send(ObjectBase knxObject)
        {
            if (KNXConnection != null)
            {
                EIBBuffer buffer = new EIBBuffer();
                buffer.data = new byte[2];
                buffer.data[0] = 0;
                //buffer.data[1] = Convert.ToByte((byte)(isWrite ? 0x80 : 0x40));
                buffer.data[1] = Convert.ToByte((byte)(0x80));
                //Value.AdaptBufferBeforeSending(isWrite, buffer);

                IValueConverter converter = new ValueConverter();
                buffer = converter.FillBuffer(knxObject, buffer);

                Write(knxObject.GroupAddress, buffer);
            }
        }

        #region Properties

        EIBConnection m_KNXConnection = null;
        EIBConnection KNXConnection
        {
            get
            {
                if (m_KNXConnection == null)
             
                    m_KNXConnection = new EIBConnection(HostName, Port);

                return m_KNXConnection;
            }
            set { m_KNXConnection = value; }
        }      

        public string HostName { get; set; }

        public int Port { get; set; }

        public bool IsRunning { get; set; }

        Thread m_ListeningThread;
        Thread ListeningThread { 
            get 
            {
                if (m_ListeningThread == null)
                {
                    m_ListeningThread = new Thread(new ParameterizedThreadStart(OpenGroupSocket));
                }
                return m_ListeningThread;
            } 
        }

        #endregion

        public ObjectBase GetKNXObject(string id)
        {
            ObjectBase foundObject = SetupObjectsList.FirstOrDefault<ObjectBase>(x => x.ID == id);
            if (foundObject != null)
            {
                if (Read(foundObject.GroupAddress))
                {
                    IValueConverter converter = new ValueConverter();
                    converter.FillObjectValue(foundObject, ReadBuffer);
                }
                else
                    throw new ApplicationException("Cannot read the value of this object : " + id);
            }
            else
                throw new ApplicationException("Cannot found an object with this ID : " + id);

            return foundObject;            
        }

        public bool SendKNXValue(string id, string value)
        {
            ObjectBase foundObject = SetupObjectsList.FirstOrDefault<ObjectBase>(x => x.ID == id);
            bool retValue = false;
            if (foundObject != null)
            {
                foundObject.SetValue(value);
                retValue = SendKNXObject(foundObject);
            }
            else
                throw new ApplicationException("Cannot found an object with this ID : " + id);

            return retValue;
        }

        public bool SendKNXObject(ObjectBase knxObject)
        {
            bool retValue = false;

            if (knxObject != null)
            {
                if (((knxObject.Flags & FlagsEnum.Transmit) == FlagsEnum.Transmit) && ((knxObject.Flags & FlagsEnum.Communication) == FlagsEnum.Communication))
                {
                    Send(knxObject);
                }
            }

            return retValue;
        }
        
    }
}
